/*
DigitalFlipbook Source Code License Agreement
Copyright(c) 2008 DigitalFlipbook. All rights reserved.
	
Please read this Source Code License Agreement carefully before using
the source code.
	
DigitalFlipbook grants to you a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable copyright license, to reproduce,
prepare derivative works of, publicly display, publicly perform, and
distribute this source code and such derivative works in source or
object code form without any attribution requirements.
	
The name "DigitalFlipbook" must not be used to endorse or promote products
derived from the source code without prior written permission.
	
You agree to indemnify, hold harmless and defend DigitalFlipbook from and
against any loss, damage, claims or lawsuits, including attorney's
fees that arise or result from your use or distribution of the source
code.
	
THIS SOURCE CODE IS PROVIDED "AS IS" AND "WITH ALL FAULTS", WITHOUT
ANY TECHNICAL SUPPORT OR ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ALSO, THERE IS NO WARRANTY OF
NON-INFRINGEMENT, TITLE OR QUIET ENJOYMENT. IN NO EVENT SHALL DIGITALFLIPBOOK
OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOURCE CODE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.digitalflipbook.webapis.scribd.methodgroups
{
	import com.adobe.crypto.MD5;
	import com.digitalflipbook.webapis.scribd.*;
	import com.digitalflipbook.webapis.scribd.events.*;
	
	import flash.events.DataEvent;
	import flash.events.Event;
	import flash.net.FileReference;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.xml.*;

	/**
	 * Contains helper functions for the method group classes.
	 */
	internal class MethodGroupHelper
	{
		/** 
		 * The request's sequence number. Each successive call for any session 
		 * must use a sequence number greater than the last.
		 */
		internal static var call_id:int = 0;

		/** 
		 * Set the internal DefaultXMLNamespace property to the scribd namespace.
		 */
		default xml namespace = scribd;
		
		/**
		 * Reusable method that the "method group" classes can call to invoke a
		 * method on the API.
		 *
		 * @param callBack The function to be notified when the RPC is complete
		 * @param method The name of the method to invoke ( like scribd.test.echo )
		 * @param signed A boolean value indicating if the method call needs
		 *			an api_sig attached to it
		 * @param params An array of NameValuePair or primitive elements to pass
		 *			as parameters to the remote method
		 * @langversion ActionScript 3.0
		 * @playerversion Flash 9.0
		 * @tiptext
		 */
		internal static function invokeMethod( service:ScribdService, 
												callBack:Function, method:String, 
												signed:Boolean, ... params:Array ):void
		{
			
			// Create an array to store our name/value pairs
			// for the query because during signing we need to sort
			// these alphabetically
			var args:Array = new Array();
			var arg:NameValuePair;
			
			args.push( new NameValuePair( "api_key", service.api_key ) );
			args.push( new NameValuePair( "method", method ) );
			
			// Loop over the params and add them as arguments
			for( var i:int=0; i<params.length; i++ )
			{
				// Do we have an argument name, or do we create one
				if( params[ i ] is NameValuePair )
				{
					args.push( params[ i ] );
				}
				else
				{
					// Create a unique argument name using our loop counter
					args.push( new NameValuePair( "param" + i, params[ i ].toString() ) );
				}
			}

			// If a user is authenticated, automatically add their session_key
			if( service.session_key )
			{
				args.push( new NameValuePair( "session_key", service.session_key ) );
				// auto-sign the call because the user is authenticated
				signed = true;
			}
			
			// Sign the call
			if( signed && service.secret )
			{
				// sign the call according to the documentation
				// here: http://www.scribd.com/platform/documentation/api?method_name=Signing
				
				args.sortOn( "name" );
				var api_sig:String = service.secret;
				for each( arg in args )
				{
					var name:String = arg.name ? arg.name.toString() : "";
					var value:String = arg.value ? arg.value.toString() : "";
					if( name == "file" ) continue;
					if( value.length==0 ) continue;
					api_sig += name + value;
				}
				args.push( new NameValuePair( "api_sig", MD5.hash( api_sig ) ) );
			}
			
			var query:String = "";
			var request:URLRequest = new URLRequest();
			var fileReference:FileReference;
			
			for( i=0; i<args.length; i++ )
			{
				arg = args[ i ] as NameValuePair;
				name = arg.name ? arg.name.toString() : "";
				value = arg.value ? arg.value.toString() : "";
				if( name == "file" )
				{
					fileReference = arg.data;
					continue;
				}
				if( value.length==0 ) continue;
				query += name + "=" + escape( value ); 
				if( i<args.length-1 ) query += "&";
			}
			
			request.url = ScribdService.END_POINT + query;
			
			if( method != "docs.upload" )
			{
				// Use the "internal" scribdservice namespace to be able to
				// access the urlLoader so we can make the request.
				var loader:URLLoader = service.scribdservice_internal::urlLoader;
				loader.addEventListener( Event.COMPLETE, callBack );
				loader.dataFormat = URLLoaderDataFormat.TEXT;
				loader.load( request );
			}
			else
			{
				fileReference.addEventListener( DataEvent.UPLOAD_COMPLETE_DATA, callBack );
				request.method = URLRequestMethod.POST;
				fileReference.upload( request, "file" );
			}
			
		}
		
		/**
		 * Handle processing the result of the API call.
		 *
		 * @param service The ScribdService associated with the method group
		 * @param response The XML response we got from the loader call
		 * @param result The event to fill in the details of and dispatch
		 * @param propertyName The property in event.data that the results should be placed
		 * @param parseFunction The function to parse the response XML with
		 * @langversion ActionScript 3.0
		 * @playerversion Flash 9.0
		 * @tiptext
		 */
		internal static function processAndDispatch( service:ScribdService, response:String, result:ScribdResultEvent, propertyName:String = "", parseFunction:Function = null ):void
		{
			// Process the response to create an object for return values
			var rsp:Object = processResponse( response, propertyName, parseFunction );

			// Copy some properties from the response to the result event
			result.success = rsp.success;
			result.data = rsp.data;
			
			// Notify everyone listening
			service.dispatchEvent( result );
		}
		
		/**
		 * Reusable method that the "method group" classes can call
		 * to process the results of the Scribd method.
		 *
		 * @param scribdResponse The rest response string that aligns
		 *		with the documentation here: 
		 *			http://www.scribd.com/platform/
		 * @param parseFunction A reference to the function that should be used
		 *		to parse the XML received from the server
		 * @param propertyName The name of the property to put the parsed data in.
		 *  	For example, the result object will contain a "data" property that
		 * 		will contain an additional property (the value of propertyName) that
		 * 		contains the actual data.
		 * @return An object with success and data properties.  Success
		 *		will be true if the call was completed, false otherwise.
		 *		Data will contain either an array of NameValuePair (if
		 *		successful) or errorCode and errorMessage properties.
		 * @langversion ActionScript 3.0
		 * @playerversion Flash 9.0
		 * @tiptext
		 */
		internal static function processResponse( scribdResponse:String, propertyName:String, parseFunction:Function ):Object
		{
			var result:Object = new Object();
			result.data = new Object();
			
			// Use an XMLDocument to convert a string to XML
			var rsp:XML = XML( scribdResponse );
			
			if ( rsp.@stat != "fail" )
			{
				result.success = true;
				
				// Parse the XML object into a user-defined class (This gives us
				// nice code hinting, and abstracts away the ScribdAPI a bit - if
				// the ScribdAPI changes responses we can modify this service
				// without the user having to update their program code
				if ( parseFunction == null )
				{
					// No parse function speficied, just pass through the XML data.
					// Construct an object that we can access via E4X since
					// the result we get back from Scribd is an xml response
					result.data = rsp;
				}
				else
				{
					result.data = parseFunction( rsp );
				}			
								
			}
			else
			{
				result.success = false;
				
				// In the event that we don't get an xml object
				// as part of the error returned, just
				// use the plain text as the error message
				
				var error:ScribdError = new ScribdError();
				if ( rsp.error != null )
				{
					var errorResponse:XMLList = rsp.error;
					error.errorCode = int( errorResponse.@code );
					error.errorMessage = errorResponse.@message;
					
					result.data = error;
				}
				else 
				{
					error.errorCode = -1;
					error.errorMessage = rsp.toString();
					
					result.data = error;
				}
			}
			return result;			
		}
		
		/**
		 * Converts a date object into a Scribd date string
		 *
		 * @param date The date to convert
		 * @return A string representing the date in a format
		 *		that Scribd understands
		 * @langversion ActionScript 3.0
		 * @playerversion Flash 9.0
		 * @tiptext
		 */
		internal static function dateToString( date:Date ):String
		{
			// Don't do anything if the date is invalid
			if ( date == null )
			{
				return "";
			}
			else
			{
				return date.getFullYear() + "-" + ( date.getMonth() + 1 )
					+ "-" + date.getDate() + " " + date.getHours()
					+ ":" + date.getMinutes() + ":" + date.getSeconds();
			}
		}
		
		/**
		 * Converts a Scribd date string into a date object
		 *
		 * @param date The string to convert
		 * @langversion ActionScript 3.0
		 * @playerversion Flash 9.0
		 * @tiptext
		 */
		internal static function stringToDate( str:String = "" ):Date
		{
			if ( str == "" )
			{
				return null;
			}
			
			var date:Date = new Date();
			// split the date into date / time parts
			var parts:Array = str.split( " " );
			
			// See if we have the xxxx-xx-xx xx:xx:xx format
			if ( parts.length == 2 )
			{
				var dateParts:Array = parts[0].split( "-" );
				var timeParts:Array = parts[1].split( ":" );
				
				date.setFullYear( dateParts[0] );
				date.setMonth( dateParts[1] - 1 ); // subtract 1 (Jan == 0)
				date.setDate( dateParts[2] );
				date.setHours( timeParts[0] );
				date.setMinutes( timeParts[1] );
				date.setSeconds( timeParts[2] );
			}
			else
			{
				// Create a date based on # of seconds since Jan 1, 1970 GMT
				date.setTime( parseInt( str ) * 1000 );
			}
			return date;
		}
		
		/**
		 * Converts an docs_changeSettings XML object into a string (the docs_changeSettings value)
		 */
		internal static function parseDocsChangeSettings( xml:XML ):String
		{
			return xml.toString();
		}
		
		/**
		 * Converts an docs_delete XML object into a string (the docs_delete value)
		 */
		internal static function parseDocsDelete( xml:XML ):String
		{
			return xml.toString();
		}
		
		/**
		 * Converts a docs_getConversionStatus XML object into a string (the docs_getConversionStatus value)
		 */
		internal static function parseDocsGetConversionStatus( xml:XML ):String
		{
			return xml.conversion_status.toString();
		}
		
		/**
		 * Converts a docs_getList XML object into an array of Document instances
		 */
		internal static function parseDocsGetList( xml:XML ):Array
		{
			var documents:Array = new Array();
			
			for each ( var r:XML in xml.resultset.result )
			{
				var document:Document = new Document();
				document.doc_id = parseInt( r.doc_id );
				document.title = r.title.toString();
				document.description = r.description.toString();
				document.access_key = r.access_key.toString();
				document.conversion_status = r.conversion_status.toString();
				
				documents.push( document );
			}
			
			return documents;
		}
		
		/**
		 * Converts a docs_getSettings XML object into a Document instance
		 */
		internal static function parseDocsGetSettings( xml:XML ):Document
		{
			var document:Document = new Document();
			document.doc_id = parseInt( xml.doc_id );
			document.title = xml.title.toString();
			document.description = xml.description.toString();
			document.access = xml.access.toString();
			document.tags = xml.tags.toString();
			document.show_ads = xml.show_ads.toString();
			document.license = xml.license.toString();
			document.access_key = xml.access_key.toString();
			document.secret_password = xml.secret_password.toString();

			return document;		
		}
		
		/**
		 * Converts a docs_search XML object into a SearchResults array of Document instances
		 */
		internal static function parseDocsSearch( xml:XML ):SearchResults
		{
			var searchResults:SearchResults = new SearchResults();
			searchResults.totalResultsAvailable = parseInt( xml.result_set.@totalResultsAvailable );
			searchResults.totalResultsReturned = parseInt( xml.result_set.@totalResultsReturned );
			searchResults.firstResultPosition = parseInt( xml.result_set.@firstResultPosition );
			
			for each ( var r:XML in xml.result_set.result )
			{
				var document:Document = new Document();
				document.doc_id = parseInt( r.doc_id );
				document.secret_password = r.secret_password.toString();
				document.access_key = r.access_key.toString();
				document.title = r.title.toString();
				document.description = r.description.toString();
				document.tags = r.tags.toString();
				document.license = r.license.toString();
				document.thumbnail_url = r.thumbnail_url.toString();
				
				searchResults.push( document );
			}
			
			return searchResults;
		}
		
		/**
		 * Converts a docs_upload XML object into a Document instance
		 */
		internal static function parseDocsUpload( xml:XML ):Document
		{
			var document:Document = new Document();
			document.doc_id = parseInt( xml.doc_id );
			document.access_key = xml.access_key.toString();
			document.secret_password = xml.secret_password.toString();

			return document;
		}
		
		/**
		 * Converts a docs_uploadFromUrl XML object into a Document instance
		 */
		internal static function parseDocsUploadFromUrl( xml:XML ):Document
		{
			var document:Document = new Document();
			document.doc_id = parseInt( xml.doc_id );
			document.access_key = xml.access_key.toString();
			document.secret_password = xml.secret_password.toString();

			return document;		
		}
		
		/**
		 * Converts a user_login XML object into a Session instance
		 */
		internal static function parseUserLogin( xml:XML ):Session
		{
			var session:Session = new Session();
			session.session_key = xml.session_key.toString();
			session.name = xml.name.toString();
			session.username = xml.username.toString();
			session.user_id = parseInt( xml.user_id );

			return session;
		}
		
		/**
		 * Converts a user_signup XML object into a Session instance
		 */
		internal static function parseUserSignup( xml:XML ):Session
		{
			var session:Session = new Session();
			session.session_key = xml.session_key.toString();
			session.name = xml.name.toString();
			session.username = xml.username.toString();
			session.user_id = parseInt( xml.user_id );

			return session;		
		}

	}
	
}
